import allure
import inspect
import re
import yaml
from functools import wraps
from time import sleep, time
from selenium import webdriver
from selenium.common.exceptions import ElementClickInterceptedException, ElementNotInteractableException, \
    TimeoutException, StaleElementReferenceException
from selenium.webdriver import ActionChains, DesiredCapabilities
from selenium.webdriver.common.alert import Alert
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait
from utils.configurator import config
from utils.logger import logger
from selenium.webdriver.common.keys import Keys

class BasePage:
    execute_in_batch = False

    # def __init__(self, driver: WebDriver = None):
    #     self.driver=None
        # 调试模式,生产环境中需要注释掉
        # options = webdriver.ChromeOptions()
        # options.debugger_address = '127.0.0.1:9222'
        # driver = webdriver.Chrome(options=options)
        # driver.implicitly_wait(20)
        # self.driver = driver
        # self.driver.refresh()


    def __init__(self, driver: WebDriver = None):
        # 获取浏览器驱动配置
        driver_config = config["driver_config"]
        # 定义浏览器
        browser = driver_config["browser"]
        if browser == "Chrome":
            # 定义浏览器配置参数
            chrome_options = webdriver.ChromeOptions()
            # 判断是否使用代理
            if config["proxy"]:
                chrome_options.add_argument(f'--proxy-server={config["proxy"]}')
            # 判断是否使用无界面运行
            if driver_config["using_headless"]:
                chrome_options.add_argument('--no-sandbox')
                chrome_options.add_argument('--disable-dev-shm-usage')
                chrome_options.add_argument("--headless")
                chrome_options.add_argument('blink-settings=imagesEnabled=false')
                chrome_options.add_argument('--disable-gpu')
            # 判断是否开启调试模式
            if driver_config["debugger"]["using_debugger"]:
                chrome_options.debugger_address = driver_config["debugger"]["debugger_address"]
            # 判断是否使用分布执行
            if driver_config["grid"]["using_grid"]:
                self.driver = webdriver.Remote(command_executor=driver_config["grid"]["remote"], desired_capabilities=DesiredCapabilities.CHROME)
            if not driver_config["grid"]["using_grid"]:
                self.driver = webdriver.Chrome(options=chrome_options)
        self.driver.maximize_window()
        self.driver.implicitly_wait(config["driver_config"]["default_implicit_waiting_time"])
        self.driver.get(config["web_url"])
        self.login_with_cookies()

    def login_with_cookies(self, fs_cookies=None):
        # self.driver.get("https://test-ccg00sirf18q.feishu.cn/admin/index")
        # cookies = self.driver.get_cookies()
        # print(cookies)
        # fs_cookies = [{'domain': '.feishu.cn', 'expiry': 1659414226, 'httpOnly': True, 'name': 't_beda37', 'path': '/', 'secure': True, 'value': 'e4d188e97f5ffe9415ec3f8a90178c0e3e84d4f398fe8c2e993b8383952a33ae'}, {'domain': '.feishu.cn', 'expiry': 1659414226, 'httpOnly': False, 'name': 'swp_csrf_token', 'path': '/', 'secure': True, 'value': '38ae8b48-f628-4d17-a023-5798f05b742d'}, {'domain': '.feishu.cn', 'expiry': 1658291026, 'httpOnly': True, 'name': 'm_65f68ea2', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': '64353662343965362d366561352d343937632d386664372d61383565653663346332663946111c051538e0cc44e1d1f309c607eaed4c1984060d7ebff8b389a1905e6d7d'}, {'domain': '.feishu.cn', 'expiry': 1718789179, 'httpOnly': True, 'name': 'QXV0aHpDb250ZXh0', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': '165028e4695c46ba9ed6624b87846dbf'}, {'domain': '.feishu.cn', 'expiry': 1658291026, 'httpOnly': False, 'name': 'csrf_token', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': 'd56b49e6-6ea5-497c-8fd7-a85ee6c4c2f9'}, {'domain': '.feishu.cn', 'expiry': 1721190226, 'httpOnly': True, 'name': 'passport_web_did', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': '7110516554546266116'}, {'domain': '.feishu.cn', 'expiry': 1663322147, 'httpOnly': False, 'name': '__tea__ug__uid', 'path': '/', 'secure': True, 'value': '1253521624953606464'}, {'domain': '.feishu.cn', 'expiry': 1718618203, 'httpOnly': True, 'name': 'session', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'XN0YXJ0-3e0ja2a0-24d4-4246-b451-6c230ec028e5-WVuZA'}, {'domain': '.feishu.cn', 'expiry': 3803029797, 'httpOnly': True, 'name': 'trust_browser_id', 'path': '/', 'secure': True, 'value': '9d426c51-b07b-4e31-913d-96d168add744'}, {'domain': '.feishu.cn', 'expiry': 1718618203, 'httpOnly': True, 'name': 'session_list', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'XN0YXJ0-3e0ja2a0-24d4-4246-b451-6c230ec028e5-WVuZA'}, {'domain': '.feishu.cn', 'expiry': 1689654226, 'httpOnly': False, 'name': 'locale', 'path': '/', 'secure': True, 'value': 'zh-CN'}]
        fs_cookies = [{'domain': '.feishu.cn', 'expiry': 1663928814, 'httpOnly': False, 'name': 'swp_csrf_token', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'c80558e0-0e9f-466d-91ca-1a885d86d2a3'}, {'domain': '.feishu.cn', 'expiry': 1725704452, 'httpOnly': True, 'name': 'session_list', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'XN0YXJ0-697i64ec-4ad5-4e45-aed6-c3728b597dfe-WVuZA_XN0YXJ0-697i64ec-4ad5-4e45-aed6-c3728b597dfe-WVuZA'}, {'domain': '.feishu.cn', 'expiry': 1725704452, 'httpOnly': False, 'name': '_ga', 'path': '/', 'secure': False, 'value': 'GA1.1.1562602552.1662622148'}, {'domain': '.feishu.cn', 'expiry': 1725704814, 'httpOnly': True, 'name': 'passport_web_did', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': '7110516554546266116'}, {'domain': '.feishu.cn', 'expiry': 1725704452, 'httpOnly': True, 'name': 'session', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'XN0YXJ0-697i64ec-4ad5-4e45-aed6-c3728b597dfe-WVuZA'}, {'domain': '.feishu.cn', 'expiry': 1662805656, 'httpOnly': False, 'name': 'csrf_token', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': '61aa7e2d-14db-4137-a65a-296c163e5b20'}, {'domain': '.feishu.cn', 'expiry': 1662803357, 'httpOnly': False, 'name': 'landing_url', 'path': '/', 'secure': True, 'value': 'https://test-ccg00sirf18q.feishu.cn/accounts/page/login?redirect_uri=https%3A%2F%2Ftest-ccg00sirf18q.feishu.cn%2Fadmin%2Findex&app_id=13'}, {'domain': '.feishu.cn', 'expiry': 1662805656, 'httpOnly': True, 'name': 'm_65f68ea2', 'path': '/', 'sameSite': 'Lax', 'secure': True, 'value': '36316161376532642d313464622d343133372d613635612d32393663313633653562323046111c051538e0cc44e1d1f309c607eaed4c1984060d7ebff8b389a1905e6d7d'}, {'domain': 'wjvzp7x5tz.feishu.cn', 'expiry': 1667816293, 'httpOnly': False, 'name': 's_v_web_id', 'path': '/', 'secure': False, 'value': 'verify_l7swc8gn_iqb5OtP4_aLrn_44Sk_9ecO_v6LSPgDyLDvR'}, {'domain': '.feishu.cn', 'expiry': 1725704452, 'httpOnly': False, 'name': '_ga_VPYRHN104D', 'path': '/', 'secure': False, 'value': 'GS1.1.1662632293.1.1.1662632452.0.0.0'}, {'domain': '.feishu.cn', 'expiry': 1670408294, 'httpOnly': False, 'name': '__tea__ug__uid', 'path': '/', 'secure': False, 'value': '7140951270395545129'}, {'domain': '.feishu.cn', 'expiry': 1662718702, 'httpOnly': False, 'name': '_gid', 'path': '/', 'secure': False, 'value': 'GA1.2.1536582556.1662632293'}, {'domain': '.wjvzp7x5tz.feishu.cn', 'expiry': 1725704294, 'httpOnly': False, 'name': '_ga', 'path': '/', 'secure': False, 'value': 'GA1.3.408598172.1662632293'}, {'domain': '.feishu.cn', 'expiry': 1725704292, 'httpOnly': True, 'name': 'QXV0aHpDb250ZXh0', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'a6a33cc8a73044f9b155a6227e7026d1'}, {'domain': '.feishu.cn', 'expiry': 3810115949, 'httpOnly': True, 'name': 'trust_browser_id', 'path': '/', 'secure': False, 'value': '471ce520-d809-4b71-a776-99d19d0fedd8'}, {'domain': '.feishu.cn', 'expiry': 1694168856, 'httpOnly': False, 'name': 'locale', 'path': '/', 'secure': True, 'value': 'zh-CN'}, {'domain': '.feishu.cn', 'expiry': 1663928814, 'httpOnly': True, 'name': 't_beda37', 'path': '/', 'sameSite': 'None', 'secure': True, 'value': 'd88c8987664255988074f23500df0d6bbd29a20bb584b2a472b2680ee90f9602'}, {'domain': '.wjvzp7x5tz.feishu.cn', 'expiry': 1662718694, 'httpOnly': False, 'name': '_gid', 'path': '/', 'secure': False, 'value': 'GA1.3.1536582556.1662632293'}]
        for cookie in fs_cookies:
            self.driver.add_cookie(cookie)
        self.driver.refresh()

    # timeout时间内轮巡操作???
    def handle_stale_element(timeout):
        def decorator(func):
            @wraps(func)
            def wrapper(self, *args, **kwargs):
                end_time = time() + timeout
                while time() < end_time:
                    try:
                        return func(self, *args, **kwargs)
                    except StaleElementReferenceException:
                        pass
            return wrapper
        return decorator

    def find(self, locator):
        return self.driver.find_element(*locator)

    def finds(self, locator):
        return self.driver.find_elements(*locator)

    def wait(self, timeout, condition, message=""):
        WebDriverWait(self.driver, timeout).until(condition, message)

    # @handle_stale_element(10)
    def click_element(self, locator):
        """点击左键"""
        element = self.find(locator)
        try:
            element.click()
        # 元素重叠被拦截用js
        except ElementClickInterceptedException:
            self.driver.execute_script("arguments[0].click();", element)
        # 向上滑动滚动条，跳转到目标元素处
        except ElementNotInteractableException:
            self.driver.execute_script("arguments[0].scrollIntoView(false);", element)
            WebDriverWait(self.driver, 5).until(ec.element_to_be_clickable(locator)).click()

    @handle_stale_element(10)
    def context_click(self, locator):
        """点击右键"""
        element = self.find(locator)
        self.driver.execute_script("arguments[0].scrollIntoView(false);", element)
        ActionChains(self.driver).context_click(element).perform()

    def click_perform(self, locator):
        element = self.find(locator)
        ActionChains(self.driver).click(element).perform()

    @handle_stale_element(10)
    def move_to_element(self, locator):
        """鼠标悬停"""
        elements = self.find(locator)
        ActionChains(self.driver).move_to_element(elements).perform()
        # sleep(3)

    @handle_stale_element(10)
    def input_text(self, locator, text, pause=0):
        element = self.find(locator)
        self.driver.execute_script("arguments[0].scrollIntoView(false);", element)
        # self.driver.execute_script("arguments[0].value='';", element)
        # element.clear()
        element.send_keys(Keys.CONTROL, 'a')
        sleep(1)
        element.send_keys(text)
        sleep(pause)

    @handle_stale_element(10)
    def get_text(self, locator):
        # 处理弹窗
        # self.wait_for_element_visible(locator)
        # 多个元素时进行批量执行
        if self.execute_in_batch:
            text_list = []
            elements = self.finds(locator)
            for element in elements:
                text_list.append(element.text)
            return text_list
        return self.find(locator).text

    def check_element_visibility(self, locator):
        elements = self.finds(locator)
        return len(elements) > 0

    def check_element_invisibility(self, locator):
        elements = self.finds(locator)
        return len(elements) == 0

    def get_attribute(self, locator, element_attribute):
        if self.execute_in_batch:
            attribute_list = []
            elements = self.finds(locator)
            for element in elements:
                attribute_list.append(element.get_attribute(element_attribute))
            return attribute_list
        return self.find(locator).get_attribute(element_attribute)

    def wait_for_element_visible(self, locator, timeout=10):
        self.driver.implicitly_wait(0.5)
        wait_condition = ec.visibility_of_element_located(locator)
        try:
            self.wait(timeout, wait_condition, f"Element {locator} is still invisible")
        except TimeoutException as e:
            raise e
        finally:
            self.driver.implicitly_wait(config["driver_config"]["default_implicit_waiting_time"])

    def wait_for_element_invisible(self, locator, timeout=10):
        self.driver.implicitly_wait(0.5)
        elements = self.finds(locator)
        if len(elements) > 0:
            wait_condition = ec.invisibility_of_element_located(locator)
            try:
                self.wait(timeout, wait_condition, f"Element {locator} is still visible")
            except TimeoutException as e:
                raise e
            finally:
                self.driver.implicitly_wait(config["driver_config"]["default_implicit_waiting_time"])
        self.driver.implicitly_wait(config["driver_config"]["default_implicit_waiting_time"])

    def wait_for_text_to_be_element_attribute(self, locator, element_attribute, text, timeout=5):
        def wait_condition(driver):
            try:
                attribute_value = driver.find_element(*locator).get_attribute(element_attribute)
                return attribute_value == text
            except StaleElementReferenceException:
                return False

        self.driver.implicitly_wait(0.5)
        try:
            self.wait(timeout, wait_condition, f"Element attribute [{element_attribute}] is not equal to {text}")
        except TimeoutException as e:
            raise e
        finally:
            self.driver.implicitly_wait(config["driver_config"]["default_implicit_waiting_time"])

    def capture_screenshot(self):
        logger.debug("Capturing screenshot...")
        return self.driver.get_screenshot_as_png()

    def execute_script(self, locator, script):
        element = None
        if locator:
            element = self.find(locator)
        return self.driver.execute_script(script, element)

    def handle_alert(self, alert_type, action, text=None):
        """弹窗处理"""
        # 判断是否存在弹窗
        alert: Alert = WebDriverWait(self.driver, 10).until(ec.alert_is_present())
        logger.debug(alert)
        alert_text = alert.text
        if alert_type != "alert" and alert_type != "confirm" and alert_type != "prompt":
            raise ValueError(f"Unsupported alert type: [{alert_type}]")
        # 处理prompt:确定、取消、输入文本（send_keys(text)）
        if text and alert_type == "prompt":
            alert.send_keys(text)
        # 处理操作:确定
        if action == "accept":
            alert.accept()
        # 处理操作:取消
        elif action == "dismiss":
            alert.dismiss()
        # 其它类型不支持，抛出异常
        else:
            raise ValueError(f"The alert type [{alert_type}] doesn't support the action [{action}]")
        # 返回alert/confirm/prompt中的文字信息
        return alert_text

    def execute_command(self, command: str, locator: list, params: dict):
        if command == "pause":
            sleep(params["time"])
            # 显示执行参数
            # logger.info(f'params["time"]:{params["time"]}')
            return None
        else:
            func = eval(f"self.{command}")
            # 检查执行操作需要的参数，如：self.input_text(locator, text) 需要locator, text
            func_args = list(inspect.signature(func).parameters.keys())
            # logger.info(f"locator: {locator}, type: {type(locator)}")
            func_params = {}
            for key in func_args:
                if key == "locator":
                    func_params[key] = locator
                elif key in params:
                    func_params[key] = params[key]
            # 显示执行操作
            # logger.info(f"command: {command}, func_params: {func_params}")
            return func(**func_params)

    def perform_functions(self, function_file, function_name, params=None):
        """
        执行功能：通过解析元素定位文件xxx_func.yml和参数化文件xxx_params.yml，然后调用self.execute_command()
        方法执行每一步操作
        """
        if params is None:
            params = {}
        # 保存元素的文本值，可用于断言
        response = {}

        # 解析元素定位文件xxx_funcs.yml
        pattern = re.compile(r"{(.+?)}")
        keys_check_variable = ["element", "elements", "text", "store_key", "enabled", "script"]
        # 异常诊断日志
        logger.debug(f"Perform function: {function_name}")
        # 读取XX_functions.yml
        with open(function_file, "r", encoding="utf-8") as f:
            functions = yaml.safe_load(f)
        steps = functions[function_name]
        # print(len(steps))
        # 包含有{}的用xxx_params.yml文件中的参数化字段进行替换
        for step in steps:
            # print(step)
            logger.debug(f"Execute command [{step['command']}], details: {step}")
            # 用参数值替换占位符
            for key in keys_check_variable:
                if key in step:
                    # //*[contains(text(), '{entity_name}')]
                    tmp_str = str(step[key])
                    # ['entity_name']
                    matches = pattern.findall(tmp_str)
                    for match in matches:
                        value = "" if params[match] is None else params[match]
                        tmp_str = tmp_str.replace(f"{{{match}}}", value)
                    else:
                        step[key] = tmp_str

            # 调用self.execute_command()执行操作
            with allure.step(step["step_id"]):
                # 检查出禁用操作并跳过,默认启用
                if "enabled" in step and (
                        isinstance(step["enabled"], str) and (step["enabled"] == "false" or not step["enabled"]) or
                        isinstance(step["enabled"], bool) and not step["enabled"]
                ):
                    logger.debug(f"Skip step [{step['step_id']}]")
                    continue

                # 解析元素定位方式支持：id, name, xpath, link text, partial link text, tag name, class name, css selector
                locator = None
                if "element" in step:
                    locator = step["element"].split("=", 1)
                if "elements" in step:
                    locator = step["elements"].split("=", 1)
                if locator:
                    # " " 替换为 "_" 否则报错
                    locator[0] = locator[0].replace("_", " ")

                # 执行命令
                try:
                    # if step["command"] == "click_element":
                    #     self.click_element(locator)
                    # elif step["command"] == "context_click":
                    #     self.context_click(locator)
                    # elif step["command"] == "input_text":
                    #     self.input_text(locator, step["text"])
                    # elif step["command"] == "get_text":
                    #     response[step["store_key"]] = self.get_text(locator)
                    # elif step["command"] == "get_attribute":
                    #     response[step["store_key"]] = self.get_attribute(locator, step["element_attribute"])
                    # elif step["command"] == "wait_for_element_visible":
                    #     self.wait_for_element_visible(locator, step["timeout"])
                    # elif step["command"] == "wait_for_element_invisible":
                    #     self.wait_for_element_invisible(locator, step["timeout"])
                    # elif step["command"] == "pause":
                    #     sleep(step["time"])
                    if "elements" in step:
                        self.execute_in_batch = True
                    else:
                        self.execute_in_batch = False
                    # print(locator)
                    return_value = self.execute_command(step["command"], locator, step)
                    # 每一步执行如果有返回值则保存到字典response
                    if "store_key" in step:
                        response[step["store_key"]] = return_value
                    # logger.info(f"操作步骤: {step}\n    返回值: {return_value}")
                    # logger.info(f"操作返回值: {return_value}")
                # 执行操作异常日志显示
                except Exception as e:
                    logger.error(f"Execute command [{step['command']}] error", exc_info=True)
                    # allure报告中附加报错截图
                    allure.attach(
                        self.capture_screenshot(), "Exception picture", attachment_type=allure.attachment_type.PNG)
                    raise e
        return response



if __name__ == '__main__':
    base = BasePage()
    # print(base)
    # print(1111)